<?php
require_once 'Core/OAuth/Consumer.php';
require_once 'Core/OAuth/Nonce.php';
require_once 'Core/OAuth/Token.php';

/**
 * 
 * @author Michael Krotscheck
 */
class Core_OAuth_Provider
{
	/**
	 * The number of seconds that we'll accept a specific nonce's timestamp.
	 * If the timestamp falls outside of this window, the client has to generate
	 * a new request and signature.
	 * 
	 * @var uint
	 */
	const NONCE_TIMEOUT_WINDOW = 60; // One minute
	
	/**
	 * The OAuth Provider
	 * 
	 * @var OAuthProvider
	 */
	private $provider;
	
	/**
	 * Retrieves the request object.
	 * 
	 * @see Zend_Controller_Action::getRequest()
	 */
	public function getRequest ()
	{
		$frontController = Zend_Controller_Front::getInstance();
		$request = $frontController->getRequest();
		return $request;
	}
	
	/**
	 * Creates a new instance of the OAuth provider.
	 */
	public function __construct ()
	{
		// Create the provider
		$this->provider = new OAuthProvider();
		$this->provider->consumerHandler( array($this, 'lookupConsumer') );
		$this->provider->timestampNonceHandler( array($this, 'timestampNonceChecker') );
		$this->provider->tokenHandler( array($this, 'tokenHandler') );
	}
	
	/**
	 * This provides a simple method by which we can validate an OAuth Request against
	 * our own tokens.
	 */
	public function validate_request()
	{
		try
		{
			$this->provider->isRequestTokenEndpoint(false);
			return $this->provider->checkOAuthRequest();
		}
		catch( OAuthException $E )
		{
			return OAuthProvider::reportProblem( $E );
		}
		catch( Exception $E )
		{
			return OAuthProvider::reportProblem( $E );
		}
	}
	
	/**
	 * This retrieves the User ID of the current OAuth request, if available.
	 * Else returns null.
	 * 
	 * @return uint|null
	 */
	public function get_request_owner()
	{
		return $this->token_owner;
	}
	
	/**
	 * The token's owner ID.
	 * 
	 * @var uint
	 */
	private $token_owner;
	
	
	/**
	 * The OAuth Authorize Action. This page handles our OAuth authorization event, and upon
	 * successful auth redirects us to the callback url with the appropriate verification key.
	 * 
	 * @return string|null
	 */
	public function authorize ( $oauth_token )
	{
		// Get the token
		$token = Core_OAuth_Token::factory( $oauth_token );
		
		// Is it a valid request token?
		if ( empty($token) || $token->type == Core_OAuth_Token::ACCESS)
		{
			return false;
		}
		
		// Create a verifier key for this token
		$token->verifier = substr( sha1( OAuthProvider::generateToken( 20, true ) ), 0, 6 );
		$token->save();
		
		return $token->verifier;
	}
	
	/**
	 * This action handles the request_token endpoint and provides an authorized presenter
	 * of a consumer key with the appropriate auth token.
	 */
	public function request_token ()
	{
		try
		{
			$this->provider->isRequestTokenEndpoint( true );
			$this->provider->checkOAuthRequest(); // This will throw an error.
			
			// At this point we should have a valid request. Create a new request token
			$consumer = Core_OAuth_Consumer::factory( $this->provider->consumer_key );
			
			$token = Core_OAuth_Token::factory();
			$token->token = sha1( OAuthProvider::generateToken( 20, true ) );
			$token->token_secret = md5( OAuthProvider::generateToken( 20, true ) );
			$token->type = Core_OAuth_Token::REQUEST;
			$token->oauthConsumerId = $consumer->oauthConsumerId;
			$token->callback_url = $this->provider->callback;
			$token->save();
			
			// Collect our response data
			$params = array();
			$params['oauth_token'] = $token->token;
			$params['oauth_token_secret'] = $token->token_secret;
			
			return http_build_query( $params );
		
		}
		catch( OAuthException $E )
		{
			return OAuthProvider::reportProblem( $E );
		}
		catch( Exception $E )
		{
			return OAuthProvider::reportProblem( $E );
		}
	}
	
	/**
	 * This action handles the access_token action, returning a long lived acess token now
	 * that a user has decided to grant their access.
	 */
	public function access_token ()
	{
		// oauth_token
		try
		{
			$this->provider->checkOAuthRequest();
			
			// At this point we should have a valid request. Create a new request token
			$consumer = Core_OAuth_Consumer::factory( $this->provider->consumer_key );
			
			// Create the new access token
			$token = Core_OAuth_Token::factory();
			$token->token = sha1( OAuthProvider::generateToken( 20, true ) );
			$token->token_secret = md5( OAuthProvider::generateToken( 20, true ) );
			$token->type = Core_OAuth_Token::ACCESS;
			$token->oauthConsumerId = $consumer->oauthConsumerId;
			$token->save();
			
			
			$params = array();
			$params['oauth_callback'] = $token->callback_url ? : null;
			$params['oauth_token'] = $token->token;
			$params['oauth_token_secret'] = $token->token_secret;
			return http_build_query( $params );
		} catch( OAuthException $E )
		{
			return OAuthProvider::reportProblem( $E );
		}
	}
	
	// ======================== Internal OAuth Methods ========================

	/**
	 * This method is mapped to the OAuthProvider's tokenHandler property. It is invoked
	 * whenever a token needs to be verified.
	 * 
	 * @param OAuthProvider $provider
	 */
	public function tokenHandler ( $provider )
	{
		$token = Core_OAuth_Token::factory( $provider->token );
		
		// Do we have a token.
		if( is_null( $token ) )
		{
			return OAUTH_TOKEN_REJECTED;
		}
		
		// Is this a request token?
		if( $token->type == Core_OAuth_Token::REQUEST && $token->verifier != $provider->verifier )
		{
			return OAUTH_VERIFIER_INVALID;
		}
		
		$provider->token_secret = $token->token_secret;
		
		return OAUTH_OK;
	}
	
	/**
	 * Checks the consumer
	 * 
	 * @param OAuthProvider $provider
	 */
	public function lookupConsumer ( $provider )
	{
		$consumer = Core_OAuth_Consumer::factory( $provider->consumer_key );
		
		if( $consumer )
		{
			if( $consumer->active == 0 )
			{
				return OAUTH_CONSUMER_KEY_REFUSED;
			}
			else
			{
				$provider->consumer_secret = $consumer->consumerSecret;
				$this->token_owner = $consumer->userId;
				return OAUTH_OK;
			}
		}
		
		return OAUTH_CONSUMER_KEY_UNKNOWN;
	}
	
	/**
	 * The timestamp nonce checker tries to see whether a given nonce has
	 * already been used for a given client and timestamp.
	 * 
	 * @param OAuthProvider $provider
	 */
	public function timestampNonceChecker ( $provider )
	{
		// Has this nonce been used?
		$isUsed = Core_OAuth_Nonce::hasNonce( $provider->nonce, $provider->consumer_key );
		
		if( $isUsed )
		{
			return OAUTH_BAD_NONCE;
		}
		
		// Is this a valid timestamp?
		$now = mktime();
		if( $now + self::NONCE_TIMEOUT_WINDOW < $provider->timestamp || $now - self::NONCE_TIMEOUT_WINDOW > $provider->timestamp )
		{
			return OAUTH_BAD_TIMESTAMP;
		}
		
		// Save this nonce usage.
		$consumer = Core_OAuth_Consumer::factory( $provider->consumer_key );
		$nonce = Core_OAuth_Nonce::factory($provider->nonce, $provider->consumer_key, self::NONCE_TIMEOUT_WINDOW);
		
		return OAUTH_OK;
	}
	
	/**
	 * This method creates a verifier code.
	 * 
	 * @return string
	 */
	private function createVerifier ()
	{
		$verifier = sha1( OAuthProvider::generateToken( 20, true ) );
		return $verifier;
	}
}